#include "HostWidget.h"

HostWidget::HostWidget(QWidget *parent, Network::ConnectionStatus * status) :
    QWidget(parent) {
    portStatus = Network::NOT_FORWARDED;
    connectionStatus = status;

    publicIPString = tr("Determining address");
    localIPString = tr("Determining address");
    accessManager = new QNetworkAccessManager(this);
    serverSocket = new QTcpServer(this);
    clientSocket = NULL;
    portMappingThread = new PortMappingThread(this);

    createWidgets();
    createLayouts();
    connectSignalSlots();

    getPublicIP();
    getLocalIP();

    addressActivated(PUBLIC_BOX);
    portCheckBox->setChecked(false);
}

HostWidget::~HostWidget() {
    if (clientSocket != NULL) {
        if (clientSocket->state() != QAbstractSocket::UnconnectedState) {
            clientSocket->disconnectFromHost();
            clientSocket->waitForDisconnected();
        }
    }
    portMappingThread->quit();
    portMappingThread->wait();
}

void HostWidget::createWidgets() {
    addressLabel = new QLabel(tr("Address:"));
    addressLabel->setAlignment(Qt::AlignLeft);
    addressComboBox = new QComboBox();
    addressComboBox->addItem(tr("Online Address"));
    addressComboBox->addItem(tr("LAN Address"));
    addressComboBox->addItem(tr("Local Address"));
    addressComboBox->addItem(tr("Other"));
    addressEdit = new QLineEdit();
    addressEdit->setAlignment(Qt::AlignLeft);
    hostButton = new QPushButton(tr("Host"));
    portCheckBox = new QCheckBox(tr("Port Forwarding"));
}

void HostWidget::createLayouts() {
    QVBoxLayout * mainLayout = new QVBoxLayout;
    mainLayout->addWidget(addressLabel);
    mainLayout->addWidget(addressComboBox);
    mainLayout->addWidget(addressEdit);
    mainLayout->addWidget(hostButton);
    mainLayout->addWidget(portCheckBox);
    mainLayout->setMargin(MARGINS);
    mainLayout->setAlignment(Qt::AlignCenter);
    setLayout(mainLayout);
}

void HostWidget::connectSignalSlots() {
    connect(addressEdit, SIGNAL(returnPressed()), hostButton, SLOT(click()));
    connect(hostButton, SIGNAL(clicked()), this, SLOT(hostOnClick()));
    connect(addressComboBox, SIGNAL(activated(int)), this, SLOT(addressActivated(int)));
    connect(accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(assignPublicIP(QNetworkReply*)));
    connect(serverSocket, SIGNAL(newConnection()), this, SLOT(serverNewConnection()));
    connect(portCheckBox, SIGNAL(stateChanged(int)), this, SLOT(portCheckStateChanged(int)));
    connect(portMappingThread, SIGNAL(portStatusChanged(int)), this, SLOT(changePortStatus(int)));
}

void HostWidget::hostOnClick() {
    QString addressString;
    if (portStatus == Network::FORWARDED) {
        addressString = localIPString;
    } else {
        addressString = addressEdit->text();
    }
    if (serverSocket->listen(QHostAddress(addressString), PORT)) {
        changeConnectionStatus(Network::HOSTING_WAITING);
    } else {
        QMessageBox messageBox;
        messageBox.setText(tr("Server error, could not host."));
        if (addressComboBox->currentIndex() == PUBLIC_BOX) {
            messageBox.setInformativeText(tr("Try the \"Port Forwarding\" option."));
        } else {
            messageBox.setInformativeText(tr("Check your connection."));
        }
        messageBox.setStandardButtons(QMessageBox::Ok);
        messageBox.setDefaultButton(QMessageBox::Ok);
        messageBox.exec();
    }
}

void HostWidget::portCheckStateChanged(int state) {
    portCheckBox->setEnabled(false);
    hostButton->setEnabled(false);
    if (state == Qt::Checked) {
        if (!localIPString.contains(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"))) {
            getLocalIP();
        }
        if (localIPString.contains(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"))) {
            portMappingThread->setLocalIP(localIPString);
            portMappingThread->forwardPort();
        } else {
            portErrorMessage(ADDRESS_ERROR);
            portCheckBox->setChecked(false);
            portCheckBox->setEnabled(true);
            hostButton->setEnabled(true);
        }
    } else {
        if (portStatus != Network::NOT_FORWARDED && portStatus != Network::ERROR) {
            if (!localIPString.contains(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"))) {
                getLocalIP();
            }
            if (localIPString.contains(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"))) {
                portMappingThread->setLocalIP(localIPString);
                portMappingThread->deletePort();
                addressEdit->setText(tr("Deleting port forward"));
            } else {
                portErrorMessage(ADDRESS_ERROR);
                portCheckBox->setEnabled(true);
                hostButton->setEnabled(true);
            }
        }
    }
}

void HostWidget::addressActivated(int index) {
    switch ((AddressComboBoxIndices) index) {
    case PUBLIC_BOX:
        addressEdit->setText(publicIPString);
        addressEdit->setEnabled(false);
        portCheckBox->setEnabled(true);
        if (publicIPString == tr("Unavailable")) {
            getPublicIP();
        }
        break;
    case LOCAL_BOX:
        hostButton->setEnabled(true);
        addressEdit->setText(localIPString);
        addressEdit->setEnabled(false);
        portCheckBox->setChecked(false);
        portCheckBox->setEnabled(false);
        if (localIPString == tr("Unavailable")) {
            getLocalIP();
        }
        break;
    case LOCALHOST_BOX:
        hostButton->setEnabled(true);
        addressEdit->setText(QString("127.0.0.1"));
        addressEdit->setEnabled(false);
        portCheckBox->setChecked(false);
        portCheckBox->setEnabled(false);
        break;
    case OTHER_BOX:
        hostButton->setEnabled(true);
        addressEdit->setEnabled(true);
        portCheckBox->setChecked(false);
        portCheckBox->setEnabled(false);
        addressEdit->clear();
        break;
    }
}

void HostWidget::serverNewConnection() {
    clientSocket = serverSocket->nextPendingConnection();
    connect(clientSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnect()));
    connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
    serverSocket->close();
    changeConnectionStatus(Network::HOSTING_CONNECTED);
}

void HostWidget::connectionError(QAbstractSocket::SocketError socketError) {
    if (socketError == QAbstractSocket::RemoteHostClosedError) {
    }
    emit connectionStatusChanged(Network::DISCONNECTED);
}

void HostWidget::socketDisconnect() {
    clientSocket->deleteLater();
    clientSocket = NULL;
}

void HostWidget::getPublicIP() {
    publicIPString = tr("Determining address");
    accessManager->get(QNetworkRequest(QString("http://jsonip.com/")));
}

void HostWidget::assignPublicIP(QNetworkReply * reply) {
    if(reply->error() == QNetworkReply::NoError) {
        QStringList list = QString(reply->readAll()).split('\"', QString::SkipEmptyParts);
        int index = list.indexOf(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"));
        if (index != -1) {
            publicIPString = list.takeAt(index);
        }
    } else {
        publicIPString = tr("Unavailable");
    }
    reply->deleteLater();
    if (addressComboBox->currentIndex() == PUBLIC_BOX) {
        addressEdit->setText(publicIPString);
    }
}

void HostWidget::getLocalIP() {
    localIPString = tr("Unavailable");
    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    for (int i = 0; i < list.size(); i++) {
        QString currentString = list.at(i).toString();
        if (currentString.contains(QRegExp("(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)")) && !currentString.startsWith("127")) {
            localIPString = currentString;
            break;
        }
    }
    if (addressComboBox->currentIndex() == LOCAL_BOX) {
        addressEdit->setText(localIPString);
    }
}

void HostWidget::changeConnectionStatus(Network::ConnectionStatus status) {
    switch (status) {
    case Network::DISCONNECTED:
        hostButton->setEnabled(true);
        if (serverSocket->isListening()) {
            serverSocket->close();
        }
        if (clientSocket != NULL) {
            if (clientSocket->state() != QAbstractSocket::UnconnectedState) {
                clientSocket->disconnectFromHost();
            }
        }
        if (addressComboBox->currentIndex() == PUBLIC_BOX && (portStatus == Network::NOT_FORWARDED || portStatus == Network::FORWARDED)) {
            portCheckBox->setEnabled(true);
        }
        break;
    case Network::HOSTING_WAITING:
        hostButton->setEnabled(false);
        portCheckBox->setEnabled(false);
        break;
    case Network::HOSTING_CONNECTED:
        hostButton->setEnabled(false);
        portCheckBox->setEnabled(false);
        break;
    case Network::CLIENT_WAITING:
        hostButton->setEnabled(false);
        portCheckBox->setEnabled(false);
        break;
    case Network::CLIENT_CONNECTED:
        hostButton->setEnabled(false);
        portCheckBox->setEnabled(false);
        break;
    }
    if (*connectionStatus != status) {
        *connectionStatus = status;
        emit connectionStatusChanged(status);
    }
}

void HostWidget::changePortStatus(int status) {
    portStatus = (Network::PortStatus) status;
    switch (status) {
    case Network::ERROR:
        portErrorMessage(ROUTER_ERROR);
    case Network::NOT_FORWARDED:
        if (portCheckBox->isChecked()) {
            portCheckBox->setChecked(false);
        }
        portCheckBox->setEnabled(true);
        hostButton->setEnabled(true);
        addressActivated(addressComboBox->currentIndex());
        break;
    case Network::SEARCHING:
        addressEdit->setText(tr("Searching for router"));
        break;
    case Network::VALIDATING:
        addressEdit->setText(tr("Validating router"));
        break;
    case Network::FORWARDING:
        addressEdit->setText(tr("Adding port forward"));
        break;
    case Network::FORWARDED:
        portCheckBox->setEnabled(true);
        hostButton->setEnabled(true);
        addressActivated(addressComboBox->currentIndex());
        break;
    }
}

void HostWidget::portErrorMessage(PortError error) {
    QString informative;
    QMessageBox messageBox;
    messageBox.setText(tr("Port forwarding error."));
    messageBox.setStandardButtons(QMessageBox::Ok);
    messageBox.setDefaultButton(QMessageBox::Ok);
    switch (error) {
    case ADDRESS_ERROR:
        informative = tr("Could not get the network address. ");
        break;
    case ROUTER_ERROR:
        informative = tr("Could not communicate with router. ");
        break;
    }
    if (portCheckBox->isChecked()) {
        informative = informative + tr("Port is not forwarded.");
    } else {
        informative = informative + tr("Port forwarding is not deleted.");
    }
    messageBox.setInformativeText(informative);
    messageBox.exec();
}
